﻿// -----------------------------------------------------------------------
// This class represents the Student class in the specification.
// The basic type STUDENTID in the specification will be 
// implemented as an integer.
//
// Author: Dan Buvid
// Date: April 13, 2012
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BellaCode.Collections.ObjectModel;
using System.ComponentModel;

namespace ResidencyPlacement
{
    public class Student : INotifyPropertyChanged
    {
        // The state space consists of: 
        // A name
        // An id
        // An entrance exam mark
        // A set of applications
        private string name;
        private int id;
        private int entranceExamMark;
        private ObservableHashSet<Application> applications;

        /// <summary>
        /// This is the constructor method that implements the 'Init' state.
        /// </summary>
        public Student(int id, string name)
        {
            this.id = id;
            this.name = name;
            entranceExamMark = 0;
            applications = new ObservableHashSet<Application>();

            if (!stateInvariantCheck())
                Console.WriteLine("\n Init state implemented by the constructor failed because "
                                  + "the state invariant is violated \n");
        }

        /// <summary>
        /// This method implements the state invariant check.
        /// </summary>
        /// <returns>boolean result indicating whether or not state invariant is satisfied.</returns>
        private bool stateInvariantCheck()
        {
            bool result = true;

            if (entranceExamMark < 0)
                result = false;

            // The state invariant asserts that for all applications for the student 
            //the hospital will be the same if and only if they are the same application.
            foreach (var app1 in applications)
            {
                foreach (var app2 in applications)
                {
                    if (app1.Hospital == app2.Hospital && !(app1 == app2))
                    {
                        result = false;
                    }
                }
            }
            
            // The state invariant asserts that there can be at most one
            // application for the student with OfferedAccepted status.
            int acceptedCount = 0;
            foreach (var appl in applications)
            {
                if (appl.Status == ApplicationStatus.OfferAccepted)
                    acceptedCount++;
            }

            if (acceptedCount > 1)
                result = false;

            return result;
        }

        /// <summary>
        /// This method implements the operations 'TakeEntranceExamMark' and 'ChangeEntranceExamMark'.
        /// </summary>
        /// <param name="newMark">The new mark for the student's entrance exam.</param>
        public void ChangeEntranceExamMark(int newMark)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "ChangeEntranceExamMark", "before");

            if (newMark < 0)
                throw new PreconditionException("Student", "ChangeEntranceExamMark",
                    "The entrance exam mark must be at least zero.");

            EntranceExamMark = newMark;

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "ChangeEntranceExamMark", "after");
        }

        /// <summary>
        /// This method implements the operation 'ApplyToHospital'
        /// </summary>
        /// <param name="newApplication">The application the student sends to the hospital.</param>
        public void ApplyToHospital(Application newApplication)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "ApplyToHospital", "before");

            // The precondition ensures that 
            // (i) no current application exists for the same hospital as newApplication.
            // (ii) the mark for the entrance exam must be greater than 0.
            foreach (var appl in applications)
            {
                if (appl.Hospital == newApplication.Hospital)
                    throw new PreconditionException("Student", "ApplyToHospital", 
                        "Cannot have two applications to the same hospital.");
            }

            if (entranceExamMark == 0)
                throw new PreconditionException("Student", "ApplyToHospital", 
                    "Entrance exam mark cannot be zero.");
            
            // The postcondition ensures that the new application has been added to the existing set of applications.
            newApplication.CumulativeMark = entranceExamMark;
            newApplication.Status = ApplicationStatus.Applied;
            applications.Add(newApplication);

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "ApplyToHospital", "after");
        }

        /// <summary>
        /// This method implements the operation 'AttendInterview'.
        /// </summary>
        /// <param name="whichHospital">The hospital where the interview is taking place.</param>
        /// <param name="interviewMark">The mark the hospital gives for the interview.</param>
        public void AttendInterview(int whichHospital, int interviewMark)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "AttendInterview", "before");

            if (interviewMark < 0)
                throw new PreconditionException("Student", "AttendInterview",
                    "The interview mark must be at least zero.");

            // The precondition ensures that there is an application for whichHospital 
            // currently in the set of applications and its status is Applied.
            bool precondition = false;
            foreach (var appl in applications)
            {
                if (appl.Hospital == whichHospital && appl.Status == ApplicationStatus.Applied)
                    precondition = true;
            }

            if (!precondition)
                throw new PreconditionException("Student", "AttendInterview", 
                    "There is no application for the student for the specified hospital with Applied status.");

            // The postcondition ensures the application status is updated to Interviewed
            // and the cumulative mark is updated to the sum of entranceExamMark and interviewMark.
            foreach (var appl in applications)
            {
                if (appl.Hospital == whichHospital)
                {
                    appl.Status = ApplicationStatus.Interviewed;
                    appl.CumulativeMark = entranceExamMark + interviewMark;
                }
            }

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "AttendInterview", "after");
        }

        /// <summary>
        /// This method implements the operation 'GotOffer'.
        /// </summary>
        /// <param name="whichHospital">The hospital that gave the offer.</param>
        public void GotOffer(int whichHospital)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "GotOffer", "before");

            // The precondition ensures that there is an application for whichHospital 
            // currently in the set of applications and its status is Interviewed.
            bool precondition = false;
            foreach (var appl in applications)
            {
                if (appl.Hospital == whichHospital && appl.Status == ApplicationStatus.Interviewed)
                    precondition = true;
            }

            if (!precondition)
                throw new PreconditionException("Student", "GotOffer",
                    "There is no application for the student for the specified hospital with Interviewed status.");

            // The postcondition ensures the application status is updated to HasBeenSelected.
            foreach (var appl in applications)
            {
                if (appl.Hospital == whichHospital) {
                    appl.Status = ApplicationStatus.HasBeenSelected;
                }
            }

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "GotOffer", "after");
        }

        /// <summary>
        /// This method implements the operation 'DecideOnOffer'.
        /// </summary>
        /// <param name="whichHospital">The hospital that is recieving the offer response.</param>
        /// <param name="decision">The decision of the student (true=accept, false=decline)</param>
        public void DecideOnOffer(int whichHospital, bool decision)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "DecideOnOffer", "before");

            // The precondition ensures that there is an application for whichHospital 
            // currently in the set of applications and its status is HasBeenSelected.
            bool precondition = false;

            foreach (var appl in applications)
            {
                if (appl.Hospital == whichHospital && appl.Status == ApplicationStatus.HasBeenSelected)
                    precondition = true;
            }

            if (!precondition)
                throw new PreconditionException("Student", "DecideOnOffer",
                    "There is no application for the student for the specified hospital with HasBeenAccepted status.");

            // The postcondition ensures that:
            // If the student accepts the position the application status is updated to OfferAccepted. 
            // If the student declines the position the application status is updated to Declined.
            if (decision)
            {
                var appl = applications.First(a => a.Hospital == whichHospital);
                appl.Status = ApplicationStatus.OfferAccepted;
            }
            else
            {
                var appl = applications.First(a => a.Hospital == whichHospital);
                appl.Status = ApplicationStatus.Declined;
            }

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Student", "DecideOnOffer", "after");     
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string p)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(p));
            }
        }

        public string Name
        {
            get
            {
                return this.name;
            }
        }

        public int ID
        {
            get
            {
                return this.id;
            }
        }

        public int EntranceExamMark
        {
            get
            {
                return this.entranceExamMark;
            }
            set
            {
                entranceExamMark = value;
                NotifyPropertyChanged("EntranceExamMark");
            }
        }

        public ObservableHashSet<Application> Applications
        {
            get
            {
                return this.applications;
            }
        }
    }
}
